package ;
import haxe.xml.Fast;
import misc.obj.Box;
import misc.obj.SolidMisc;
import org.flixel.FlxBasic;
import org.flixel.FlxCamera;
import org.flixel.FlxGroup;
import org.flixel.FlxSound;
import org.flixel.FlxSprite;
import org.flixel.FlxObject;
import org.flixel.addons.FlxTrail;
import org.flixel.FlxG;
import org.flixel.FlxTilemap;
import org.flixel.system.FlxTile;
import org.flixel.util.FlxPoint;
import org.flixel.FlxTypedGroup;
import org.flixel.util.FlxMath;
/**
 * ...
 * @author ...
 */
class BaseCharacter extends FlxSprite
{	
	
	public	var name:String										=	"Object";
	public 	var state(default, null): Int;
	public  var playDustEmitt:Bool								=	false;
	public	var playState:PlayState								=	null;	
	public	var vx:Float										=	0;	
	public	var dx:Int											=	1;
	public	var gravity:Float									=	0;
	//---*Flag/Bandera para saber si el Character se encuentra sobre el suelo o no:
	public	var isGrounded:Bool									=	false;	
	//---*Flag/Bandera para saber si el Character se encuentra Moviendoce sobre el eje X:
	public	var isMoveXFlag:Bool								=	false;
	//---*Flag/Bandera para saber si el Character se encuentra saltando, util para el 2ble salto:
	public	var	isJumpingFlag:Bool								=	false;
	public	var	nJump											=	0;		
	
	public	var lastFallVelocity:Float							=	0;
	//---*Si el char tiene un sonido al saltar, renombrarlo y el engine lo reproduce solito:
	public	var sourceJumpSound:String							=	"null";
	public	var CollideFlag:Int									=	0;
	
	public	var weapon:Int										=	0;
	public	var ammo:Int										=	0;
	
	public	var collideWithBox:Bool								=	false;
	public  var	collideWithTileRompible:Bool					=	false;
	public  var	collideWithCheckPoint:Bool						=	false;
	public	var isDead:Bool										=	false;
	public	var JumpVelocity:Float								=	2;
	public	var type:Dynamic									=	null;

	
	public	var platform:SolidMisc								=  	null;
	
	override public function update():Void 
	{		
		
		if (this.playState == null)
		{
			//---*Le referenciamos el state:
			this.playState				=	cast( FlxG.state, PlayState);	
		}
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// Movimiento en eje X
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		if (this.isMoveXFlag )
		{
			this.velocity.x 	=	this.vx * this.dx;		
		}	
		
		
		if (this.solid)
		{
			MoveCharacter();
		}
		
		
		//---*Vemos si se modificó la maxVelocity en Y lo volvemos a cambiar:
		if (this.gravity != 0)
		{
			if (this.velocity.y > 0 && this.maxVelocity.y == this.gravity)
			{
				this.maxVelocity.y = this.gravity / 2;
			}
		}
		
		
		
		
		if (this.platform !=null)
		{
			if (this.Touch(CharConst.FLAG_FLOOR))
			{
				//FlxG.log("EEEEIEIIEEIIE");
				this.velocity.y = 0;
				
			}
			else 
			{
				this.platform.target = null;
				this.platform = null;
			}
			
		}
		
		
		super.update();
		
		
		
		
		
		this.lastFallVelocity	= this.velocity.y;	
		
		
		//---*Evitamos que cualquier tipo de esta clase se dibuje si no está en pantalla:
		this.visible = this.onScreen(FlxG.camera);
		
	}
	/**
	 *	Salta.
	 *	Al recibir este llamado, el Objeto "Salta" creando un movimiento negativo sobre el eje Y. El Objeto solo salta si este no se encuentra tocando el suelo.
	 *	@param	vs			Velocidad con la que va a saltar el Objeto:
	 */
	public function Jump(vs:Float,playSound:Bool = true):Bool //el retorno podria ser void
	{
		this.nJump			+= 	1;
		if (this.nJump == 1)
		{
			if (this.maxVelocity.y == this.gravity)
			{
				this.maxVelocity.y = this.gravity / 2;
			}
		}
		
		if (this.platform != null)
		{
			this.platform.target = null;
			this.platform = null;
		}
		
		
		this.velocity.y 	=	-vs;					
		
		
		/*
		
		*/
		if(playSound)
			PlayJumpSound();
		return	true;//no hace falta por ahora	
	}
	
	private function MoveCharacter()
	{			
		
		
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// Movimiento en eje Y
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				
		//---*Si el Character se encuentra tocando el suelo:
		if (this.Touch(CharConst.FLAG_FLOOR))
		{		
			
			this.nJump	=	0;
			if (this.isJumpingFlag)
			{
				//---*Emite particulas de polvo si el character asi lo desea:
				if (this.playDustEmitt)
				{				
					
					this.playState.emitterController.PlayEmitter("Dust", this.x + this.width / 2, this.y + this.height, this.lastFallVelocity);
					//this.playDustEmitt = false;
				}	
			}
			this.isGrounded		=	true;	
			this.nJump			=	0;	
			this.isJumpingFlag	=	false;
			
		}	
		else
		{
			this.isGrounded		=	false;
			this.isJumpingFlag	=	true;
		}		
		
		
		this.CollideFlag		= CharConst.FLAG_NONE;
		
		//---*Con tiles:
		CheckCollideFlags(this.playState.level.SpecialTileLayer);
		CheckCollideFlags(this.playState.level.SolidTileLayer);	
		
		if (this.collideWithTileRompible)
		{
			CheckCollideFlagsWithGroup(this.playState.tilesRompiblesController);
			
		}
		
		//---*Con entidades:
		if (this.collideWithBox)
		{
			CheckCollideFlagsWithGroup(this.playState.solidMiscController);
			
		}
		
		if (this.collideWithCheckPoint)
		{
			CheckCollideFlagsWithGroup(this.playState.CheckPoints);
			
		}
		
		
		
	}
	
	/**
	 * Reproduce el sonido de salto del Character.
	 * Al recibir este llamado, este objeto ve si el character tiene un sonido al saltar, si lo tiene lo reproduce, Para hacer esto defina la ruta del sonido en el atributo "sourceJumpSound" en el CONSTRUCTOR del Character.
	 */
	public function PlayJumpSound():Void 
	{
		if (this.sourceJumpSound != "null")
		{			
			FlxG.play(this.sourceJumpSound);
		}
	}
	/**
	 * Dispara.
	 * Al recibir este llamado, el Objeto envia al PlayState que emita un disparo del Pool de disparos.
	 */
	public	function Shoot(x:Float,y:Float,vx:Float):Void
	{		
		var name:Dynamic = null;
		var vy:Float     = 0;
		
		switch(this.weapon)
		{
			case CharConst.WEAPON_SIMPLE: name = CharConst.CLASSIC_MEGAMAN_SHOOT;
			case CharConst.WEAPON_MISSILE: name = CharConst.MISSILE_SHOOT;
			case CharConst.WEAPON_AXE: name = CharConst.WONDER_BOY_SHOOT;
		}
		if (this.weapon == CharConst.WEAPON_AXE)
		{
			vy = -550;
		}
		//---*Envia la orden de disparar al Shoot Controller del state Actual:
		this.playState.shootController.PlayShoot(name, x + (this.width/2  * this.dx), y, this.dx, vx, vy);
		this.ammo -= 1;
		if (this.weapon != CharConst.WEAPON_SIMPLE)
		{
			if (this.ammo <= 0)
			{
				this.weapon = CharConst.WEAPON_SIMPLE;
			}
		}
	}
	
	
	/**
	 * Cambia el Estado de la Animacion del Character.
	 * Al recibir este llamado, el Objeto usa la funcion (sobrecargada) "GetStateNameAnimation()"
	 */
	public function ChangeState(newEstate:Int, frame:Int = 0, reset:Bool = false):Void
	{
		this.state = newEstate;			
		this.play(GetStateNameAnimation(), reset,frame);
		
			
	}
	/**
	 * Obtiene el nombre (en STRING) de la animacion del estado actual.
	 * Al recibir este llamado, el objeto ve su estado y en base a este devuelve el nombre de la animacion que le corresponde.
	 */
	public function GetStateNameAnimation():String
	{
		return "SOBRECARGAR";
	}
		
	public function CheckCollideFlagsWithGroup(group:FlxBasic):Void
	{
				
		//---*WALL:
		if (this.overlapsAt(this.x-1,this.y,group))
		{
			
			this.CollideFlag |= CharConst.FLAG_LEFT;
			this.CollideFlag |= CharConst.FLAG_WALL;
		}
		
		if (this.overlapsAt(this.x+1,this.y,group))
		{
			
			this.CollideFlag |= CharConst.FLAG_RIGHT;
			this.CollideFlag |= CharConst.FLAG_WALL;
		}
		
		//---*FLOOR:
		if (this.overlapsAt(this.x,this.y+1,group))
		{
			
			this.CollideFlag |= CharConst.FLAG_FLOOR;
			
		}
		
		//---*CEIL		
		if (this.overlapsAt(this.x,this.y-1,group))
		{
			
			this.CollideFlag |= CharConst.FLAG_CEIL;
			
		}
		
		
	}	
	
	
	//---*SE USA???
	public function onCollideEnter():Void
	{
		
		if (this.isTouching(FlxObject.FLOOR))
		{
			this.CollideFlag |= CharConst.FLAG_FLOOR;
		}
		if (this.isTouching(FlxObject.WALL))
		{
			this.CollideFlag |= CharConst.FLAG_WALL;
		}
		
	}
	
	
	public function CheckCollideFlags(ObjectOrGroup:FlxBasic):Void
	{
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// Collide Flags:
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		
		//---*Ve si ObjectOrGroup se trata de un Cmap:
		if (Std.is(ObjectOrGroup, FlxTilemap))
		{
			var tilemap:FlxTilemapExt = cast(ObjectOrGroup, FlxTilemapExt);
			CheckCollideMapOverlaps(tilemap);
		}
		
	}
	
	/**
	 * Chequea si la coordenada de los puntos en X e Y del BaseCharacter están dentro o no de un tile 1 (solido)
	 * @param	Cmap	Mapa de colision = en este caso es un objeto FlxTilemapExt
	 * @return	Nothing
	 */
	
	public function CheckCollideMapOverlaps(Cmap:FlxTilemapExt):Void
	{		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// Algunas variables locales de uso: Esquema de los puntos en el AABB
												////------[LU]=====[*]=====[RU]
												////------[*]======[*]=====[*]
												////------[LM]=====[*]=====[RM]
												////------[*]======[*]=====[*]
												////------[LD]=====[*]=====[RD]												
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		var tile_LU:Int; 		
		var tile_LM:Int; 		
		var tile_LD:Int; 		
		var tile_RU:Int; 		
		var tile_RM:Int; 		
		var tile_RD:Int; 
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// WALL:
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//---*Izquierda
		tile_LU	=	GetTile(Cmap, this.x - 	1, this.y );
		tile_LM	=	GetTile(Cmap, this.x - 	1, (this.y + this.height / 2)-1);
		tile_LD	=	GetTile(Cmap, this.x - 	1, (this.y + this.height)-1);
		//---*Derecha:
		tile_RU	=	GetTile(Cmap, this.x + this.width +1, this.y );
		tile_RM	=	GetTile(Cmap, this.x + this.width +1, (this.y + this.height / 2)-1);
		tile_RD	=	GetTile(Cmap, this.x + this.width +1, (this.y + this.height)-1);
		
			////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//----*DANGER
			////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			
			if (tile_LU == CharConst.TILE_DANGER_RIGHT || tile_LM == CharConst.TILE_DANGER_RIGHT || tile_LD == CharConst.TILE_DANGER_RIGHT)		
			{
				this.CollideFlag |= CharConst.FLAG_DANGER;
				this.CollideFlag |= CharConst.FLAG_DANGER_LEFT;
			}
			
			if (tile_RU == CharConst.TILE_DANGER_LEFT || tile_RM == CharConst.TILE_DANGER_LEFT || tile_RD == CharConst.TILE_DANGER_LEFT)		
			{
				this.CollideFlag |= CharConst.FLAG_DANGER;
				this.CollideFlag |= CharConst.FLAG_DANGER_RIGHT;
			}
			
			
		if (tile_LU == CharConst.TILE_SOLID || tile_LM == CharConst.TILE_SOLID || tile_LD == CharConst.TILE_SOLID ||
			tile_RU == CharConst.TILE_SOLID || tile_RM == CharConst.TILE_SOLID || tile_RD == CharConst.TILE_SOLID)
		{
			this.CollideFlag |= CharConst.FLAG_WALL;
			if (tile_LU == CharConst.TILE_SOLID || tile_LM == CharConst.TILE_SOLID || tile_LD == CharConst.TILE_SOLID)
			{
				this.CollideFlag |= CharConst.FLAG_LEFT;
			}
			else if (tile_RU == CharConst.TILE_SOLID || tile_RM == CharConst.TILE_SOLID || tile_RD == CharConst.TILE_SOLID)
			{
				this.CollideFlag |= CharConst.FLAG_RIGHT;
			}
		}		
		
			
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// FLOOR:
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		tile_LD	=	GetTile(Cmap, this.x , (this.y + this.height)+1);
		tile_RD	=	GetTile(Cmap, this.x + this.width -1, (this.y + this.height)+1);	
		
		
		//---*CLOUD:
		if (tile_LD == CharConst.TILE_CLOUD || tile_RD == CharConst.TILE_CLOUD )
		{
			if (this.isTouching(FlxObject.FLOOR))
			{
				this.CollideFlag |= CharConst.FLAG_FLOOR;
				this.CollideFlag |= CharConst.FLAG_CLOUD;
			}
		}
		
		
		
		
		if (tile_LD == CharConst.TILE_SOLID || tile_RD == CharConst.TILE_SOLID || 
			tile_LD == CharConst.TILE_SLOPE_SENSOR || tile_RD == CharConst.TILE_SLOPE_SENSOR)
		{			
			this.CollideFlag |= CharConst.FLAG_FLOOR;
					
		}
		
		//---*SLOPE:
		var tile_SL:Int	=	GetTile(Cmap, this.x + (this.width / 2) -1, (this.y + this.height) );
		var tile_SR:Int	=	GetTile(Cmap, this.x + (this.width / 2), (this.y + this.height) );
		
		if ( tile_SL == CharConst.TILE_SLOPE_FLOOR_LEFT || tile_SL == CharConst.TILE_SLOPE_FLOOR_RIGHT || 
			 tile_SR == CharConst.TILE_SLOPE_FLOOR_LEFT || tile_SR == CharConst.TILE_SLOPE_FLOOR_RIGHT) 
		{
			this.CollideFlag |= CharConst.FLAG_FLOOR;
			this.CollideFlag |= CharConst.FLAG_SLOPE;
			
		}
		
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// CEIL:
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		
		tile_LU	=	GetTile(Cmap, this.x+1 , (this.y )-1);
		tile_RU	=	GetTile(Cmap, this.x + this.width -1, this.y -1);
		if(tile_LU	==	CharConst.TILE_SOLID	||	tile_RU	==	CharConst.TILE_SOLID )
		{
			this.CollideFlag	|= CharConst.FLAG_CEIL;
		}	
		
		
		if (tile_LU == CharConst.TILE_KILLER || tile_LM == CharConst.TILE_KILLER || tile_LD == CharConst.TILE_KILLER ||
		tile_RU == CharConst.TILE_KILLER || tile_RM == CharConst.TILE_KILLER || tile_RD == CharConst.TILE_KILLER)
		{
			
			this.isDead = true;
			if (this.platform != null)
			{
				this.platform.target = null;
				this.platform = null;
			}
				
		}
		
	}
	
	public function GetTile(Cmap:FlxTilemapExt, x:Float, y:Float):Int
	{
		var TW:Int		=	this.playState.level.width;
		var X:Int		=	Std.int(x/16);
		var TH:Int		=	this.playState.level.height;
		var Y:Int		=	Std.int((y/16 ));		
		var tile:Int 	= 	Cmap.getTile(X, Y);
		
		/*
		//---*CLOUD		
		if (tile == CharConst.TILE_CLOUD) 
		{
			//---*Si es un tile nube nos aseguramos de que las posicion en Y
			//---*del baseCharacter sea menor a la del tile:
			var TILEY:Float = Y * 16;
			
			if (Std.int(y)-1 <= TILEY)
			{
				return tile;
			}
			else 
			{
				return 0;
			}
			
			
		}
		*/
		
		
		
		
		
		
		//---*¿Es un slope?
		//---*Si lo es, nos aseguramos que los puntos en x e y ingresados toquen
		//---*el mapa de pixeles del slope:
		//---*Slope Floor Left:
		if (tile == CharConst.TILE_SLOPE_FLOOR_LEFT) 
		{			
			var slopePointX:Float;
			var slopePointY:Float;
			var slopeX:Float = X*16;
			var slopeY:Float = Y*16;		
			slopePointX = x ;
			slopePointY = (slopeY + 16) - (slopePointX - slopeX);		
			//fix the slope point to the slope tile
			fixSlopePoint(slopePointX,slopePointY,slopeX,slopeY);
			
			if (x > slopeX  && x < slopeX + 16 + this.width / 2 && y >= (slopePointY -1))	
			{
				return tile;
			}
			else return 0;
			
		}
		//---*Slope Floor Right:
		if (tile == CharConst.TILE_SLOPE_FLOOR_RIGHT) 
		{
			
			var slopePointX:Float;
			var slopePointY:Float;
			var slopeX:Float = X*16;
			var slopeY:Float = Y*16;		
			slopePointX = x ;
			slopePointY = (slopeY + 16) - (slopeX - slopePointX + 16);		
			//fix the slope point to the slope tile
			fixSlopePoint(slopePointX,slopePointY,slopeX,slopeY);
			
			if (x > slopeX - this.width/2  && x < slopeX + 16  &&  y >= (slopePointY - 1) )
			{
				return tile;
			}
			else return 0;
			
		}	
		
		
		
		
		/*
			FlxG.log("X : ", X);
			FlxG.log("Y : ", Y);
			FlxG.log("TILE ID: ", tile);
		*/
		return tile;
	}
	
	/**
	 * bounds the slope point to the slope
	 * @param slope the slope to fix the slopePoint for
	 */
	private function fixSlopePoint(spx:Float, spy:Float,sx:Float,sy:Float ):Void
	{
		spx	= FlxMath.bound(spx, sx, sx + 16);
		spy = FlxMath.bound(spy, sy, sy + 16);
	}
	
	/**
	 * Handy function for checking if this object is touching a particular surface.
	 * @param	Direction	Any of the collision flags (e.g. LEFT, FLOOR, etc).
	 * @return	Whether the object is touching an object in (any of) the specified direction(s) this frame.
	 */
	public function Touch(Direction:Int):Bool
	{
		return (this.CollideFlag & Direction) > CharConst.FLAG_NONE;
	}
		
	
	public function FindProperty(propertyName:String, obj:Fast = null):Float
	{
		var value:Float = 1;
		if (obj != null)
		{
			
			if (obj.hasNode.properties)
			{
				
				for ( property in obj.node.properties.nodes.property) 
				{
					if (property.has.name && property.att.name == propertyName)
					{
						value = Std.parseInt(property.att.value);
						
					}
				}	
			}
		}
		return value;
	}
	
}